#include "../config.h"

#include <algorithm>
#include <gtk/gtk.h>
#include <glibmm/i18n.h>
#include <boost/format.hpp>

#include "log.h"
#include "format.h"
#include "strfuncs.h"
#include "application.h"
#include "command.h"
#include "mainwindow.h"
#include "menu.h"
#include "pref/p_dialog.h"

#include "gtkversion.h"

#ifdef ENABLE_DBUS_SERVICE
# include "dbus-defs.h"
#endif

namespace gpyp {

//========================
// MenuShell
MenuData::~MenuData() {
}

//========================
// MenuShell
void MenuShell::on_activate(CommandOpts &opts) {
  if (!slot_activate_.empty()) slot_activate_(opts);
}

void MenuShell::run_command() {
  CommandOpts opts;
  on_activate(opts);
  app_.commandrunner().run(*get_data()->command(), &opts);
}

Gtk::MenuItem *MenuShell::create_menu_item(RefData data) {
  switch(data->get_type()) {
  case ConfMenu::ITEM_TYPE_SEPARATOR:
    return Gtk::manage(new SeparatorMenuItem(data));
  case ConfMenu::ITEM_TYPE_SPECIAL:
    return Gtk::manage(new SpecialMenuItem(app_, data, sigc::mem_fun(*this, &MenuShell::on_activate)));
  default:
    return Gtk::manage(new MenuItem(app_, data, sigc::mem_fun(*this, &MenuShell::on_activate)));
  }
}
Gtk::Menu *MenuShell::create_menu() {
  return Gtk::manage(new Menu(app_, get_data(), sigc::mem_fun(*this, &MenuShell::on_activate)));
}

MenuShell::MenuShell(Application &app,
                     RefData      data,
                     SlotActivate slot_activate) :
  MenuData      (data),
  app_          (app),
  slot_activate_(slot_activate)
{
  conn_item_added_ = get_data()->signal_item_added()
    .connect(sigc::mem_fun(*this, &MenuShell::on_item_added));
  conn_item_removed_ = get_data()->signal_item_removed()
    .connect(sigc::mem_fun(*this, &MenuShell::on_item_removed));
  conn_item_reordered_ = get_data()->signal_item_reordered()
    .connect(sigc::mem_fun(*this, &MenuShell::on_item_reordered));
  conn_cleared_ = get_data()->signal_cleared()
    .connect(sigc::mem_fun(*this, &MenuShell::on_cleared));
}
MenuShell::~MenuShell() {
  conn_item_added_    .disconnect();
  conn_item_removed_  .disconnect();
  conn_item_reordered_.disconnect();
  conn_cleared_       .disconnect();
}

//========================
// MenuItem
void MenuItem::im_on_special_icon_changed(const char *icon_name) {
  if (*MenuShell::get_data()->icon() == icon_name) {
    image_.set(IconManager::load_icon(icon_name, GTK_ICON_SIZE_MENU));
  }
}

void MenuItem::on_item_added(RefData /*data*/) {
  if (!get_submenu()) {
    set_submenu(*create_menu());
  }
}
void MenuItem::on_item_removed(RefData /*data*/) {
  if (!MenuShell::get_data()->has_children() && get_submenu()) {
    remove_submenu();
  }
}
void MenuItem::on_item_reordered(RefData /*data*/,
                                 RefData /*prev*/,
                                 RefData /*next*/) {
  /* サブメニューのインスタンスで処理するので何もしない。 */
}
void MenuItem::on_cleared() {
  remove_submenu();
}

void MenuItem::on_activate() {
  if (!MenuShell::get_data()->has_children() &&
      MenuShell::get_data()->get_type() == ConfMenu::ITEM_TYPE_COMMAND) {
    run_command();
  }
  Gtk::MenuItem::on_activate();
}

void MenuItem::icon_on_changed() {
  const Glib::ustring &icon_name = *MenuShell::get_data()->icon();
  if (icon_name.empty()) {
    image_.clear();

    conn_im_special_icon_changed_.disconnect();
  } else {
    image_.set(IconManager::load_icon(icon_name, GTK_ICON_SIZE_MENU));

    conn_im_special_icon_changed_.disconnect();
    if (IconManager::is_special_icon(icon_name)) {
      conn_im_special_icon_changed_ =
        IconManager::signal_special_icon_changed()
        .connect(sigc::mem_fun(*this, &MenuItem::im_on_special_icon_changed));
    }
  }
}

MenuItem::MenuItem(Application &app,
                   RefData      data,
                   SlotActivate slot_activate) :
  MenuShell(app, data, slot_activate)
{
#ifdef GTK_MENU_ITEM_HAVE_LABEL_PROPERTY
  data->label()->attach(property_label());
  set_use_underline(true);
#else
  /* from gtkaction.c */
  Gtk::Widget *child = get_child();
  Gtk::Label  *label = dynamic_cast<Gtk::Label *>(child);
  if (!label) {
    if (child) remove();
    label = Gtk::manage(new Gtk::Label());
    add(*label);
  }
  data->label()->attach(label->property_label());
  label->set_use_underline(true);
  label->property_xalign() = 0;
  label->show();
#endif
  icon_on_changed();
  conn_icon_changed_ = data->icon()->signal_changed()
    .connect(sigc::mem_fun(*this, &MenuItem::icon_on_changed));

  if (data->has_children()) set_submenu(*create_menu());

  set_image(image_);
  show();
}
MenuItem::~MenuItem() {
  conn_icon_changed_.disconnect();
  conn_im_special_icon_changed_.disconnect();
}

//========================
// SpecialMenuItem
/* 子が追加/削除されてもサブメニューを設定したり削除したりしない。 */
void SpecialMenuItem::on_item_added(RefData /*data*/) {
}
void SpecialMenuItem::on_item_removed(RefData /*data*/) {
}
void SpecialMenuItem::on_cleared() {
}

void SpecialMenuItem::sptype_on_changed() {
  switch(*MenuShell::get_data()->sptype()) {
  case ConfMenu::SPECIAL_TYPE_PLAYWITH:
    set_submenu(*Gtk::manage(new PlayWithMenu(get_app(),
                                              get_slot_activate())));
    break;
  default:
    remove_submenu();
    break;
  }
}

SpecialMenuItem::SpecialMenuItem(Application &app,
                                 RefData      data,
                                 SlotActivate slot_activate) :
  MenuItem(app, data, slot_activate)
{
  sptype_on_changed();
  conn_sptype_changed_ = data->sptype()->signal_changed()
    .connect(sigc::mem_fun(*this, &SpecialMenuItem::sptype_on_changed));
}
SpecialMenuItem::~SpecialMenuItem() {
  conn_sptype_changed_.disconnect();
}

//========================
// SimpleMenuItem
void SimpleMenuItem::attr_icon_on_changed() {
  if ((*attr_icon_).empty()) {
    image_.clear();
  } else {
    image_.set(IconManager::load_icon(*attr_icon_, GTK_ICON_SIZE_MENU));
  }
}

SimpleMenuItem::SimpleMenuItem(const Glib::ustring &label,
                               sigc::slot<void>     slot,
                               const Glib::ustring &icon_name) :
  Gtk::ImageMenuItem(label, true),
  attr_icon_        (NULL)
{
  if (!icon_name.empty()) {
    image_.set(IconManager::load_icon(icon_name, GTK_ICON_SIZE_MENU));
  }
  set_image(image_);
  conn_activate_ = signal_activate().connect(slot);
  show();
}
SimpleMenuItem::SimpleMenuItem(const Glib::ustring       &label,
                               sigc::slot<void>           slot,
                               const Gtk::BuiltinStockID &stock_id) :
  Gtk::ImageMenuItem(label, true),
  attr_icon_        (NULL)
{
  image_.set(stock_id, Gtk::ICON_SIZE_MENU);
  set_image(image_);
  conn_activate_ = signal_activate().connect(slot);
  show();
}
SimpleMenuItem::SimpleMenuItem(conf::RefAttributeStr attr_label,
                               conf::RefAttributeStr attr_icon) :
  attr_icon_(attr_icon)
{
#ifdef GTK_MENU_ITEM_HAVE_LABEL_PROPERTY
  attr_label->attach(property_label());
  set_use_underline(true);
#else
  /* from gtkaction.c */
  Gtk::Widget *child = get_child();
  Gtk::Label  *label = dynamic_cast<Gtk::Label *>(child);
  if (!label) {
    if (child) remove();
    label = Gtk::manage(new Gtk::Label());
    add(*label);
  }
  attr_label->attach(label->property_label());
  label->set_use_underline(true);
  label->property_xalign() = 0;
  label->show();
#endif
  attr_icon_on_changed();
  conn_attr_icon_changed_ = attr_icon_->signal_changed()
    .connect(sigc::mem_fun(*this, &SimpleMenuItem::attr_icon_on_changed));
  set_image(image_);
  show();
}
SimpleMenuItem::~SimpleMenuItem() {
  conn_attr_icon_changed_.disconnect();
  conn_activate_.disconnect();
}

//========================
// SeparatorMenuItem
SeparatorMenuItem::SeparatorMenuItem(RefData data) :
  MenuData(data)
{
  show();
}

//========================
// MenuBase
void MenuBase::prepend(Gtk::MenuItem &item) {
  if (item_count_front_ == 0) {
    separator_front_ = Gtk::manage(new Gtk::SeparatorMenuItem());
    separator_front_->show();
    Gtk::Menu::prepend(*separator_front_);
    ++item_count_front_;
  }
  Gtk::Menu::prepend(item);
  ++item_count_front_;
}
void MenuBase::add(Gtk::MenuItem &item) {
  MenuList &items = Gtk::MenuShell::items();
  MenuList::iterator iter = items.end();
  for(size_t i = item_count_back_; i > 0; --i) --iter;
  items.insert(iter, item);
}
void MenuBase::append(Gtk::MenuItem &item) {
  if (item_count_back_ == 0) {
    separator_back_ = Gtk::manage(new Gtk::SeparatorMenuItem());
    separator_back_->show();
    Gtk::Menu::append(*separator_back_);
    ++item_count_back_;
  }
  Gtk::Menu::append(item);
  ++item_count_back_;
}

void MenuBase::remove(Gtk::MenuItem &target) {
  MenuList &items = Gtk::MenuShell::items();
  for(MenuList::iterator iter = items.begin();
      iter != items.end(); ++iter) {
    if (&*iter == &target) {
      items.erase(iter);
      break;
    }
  }
}
void MenuBase::reorder(int tpos, int ppos, int npos) {
  if (tpos < 0 || (ppos < 0 && npos < 0)) {
    log::dbg << "bug: MenuBase::reorder" << log::endl;
    return;
  }

  log::dbg << boost::format("%02i:%02i:%02i") % tpos % ppos % npos
           << " >> ";

  /* gtkmmのMenuListでやると、オブジェクトをコピーしても並べ替えた時に
     イテレータの指す位置が変わるのでgtk_container_get_childrenを使う */

  int i = 0;
  GList *items = gtk_container_get_children(GTK_CONTAINER(gobj()));
  for(GList *item = items; item; item = item->next) {
    int _pos = 0;
    if (i == tpos) {
      size_t pos = 0;
      if (ppos >= 0) pos = tpos > ppos ? ppos + 1 : ppos;
      else           pos = tpos < npos ? npos - 1 : npos;
      gtk_menu_reorder_child(gobj(), GTK_WIDGET(item->data), _pos = pos);
      log::dbg << "*";
    } else if (npos >= 0 && i >= npos && i < tpos) {
      gtk_menu_reorder_child(gobj(), GTK_WIDGET(item->data), _pos = i + 1);
      log::dbg << "+";
    } else if (ppos >= 0 && i > tpos && i <= ppos) {
      gtk_menu_reorder_child(gobj(), GTK_WIDGET(item->data), _pos = i - 1);
      log::dbg << "-";
    } else {
      log::dbg << " ";
    }
    log::dbg << boost::format("%02i") % _pos << " ";
    ++i;
  }
  g_list_free(items);
  log::dbg << "<<" << log::endl;
}
void MenuBase::reorder(Gtk::MenuItem &target,
                       Gtk::MenuItem *prev,
                       Gtk::MenuItem *next) {
  reorder(&target, prev, next, PointerMatch());
}
void MenuBase::clear() {
  MenuList &items = Gtk::MenuShell::items();
  MenuList::iterator begin = items.begin();
  MenuList::iterator end   = items.end();
  for(size_t i = item_count_front_; i > 0; --i) ++begin;
  for(size_t i = item_count_back_;  i > 0; --i) --end;
  if (end != begin) items.erase(begin, end);
}

MenuBase::MenuBase() :
  item_count_front_(0),
  item_count_back_ (0),
  separator_front_ (NULL),
  separator_back_  (NULL)
{
}

//========================
// Menu
void Menu::on_item_added(RefData data) {
  add(*create_menu_item(data));
}
void Menu::on_item_removed(RefData data) {
  remove(data, DataMatch());
}
void Menu::on_item_reordered(RefData data, RefData prev, RefData next) {
  reorder(data, prev, next, DataMatch());
}
void Menu::on_cleared() {
  clear();
}

Menu::Menu(Application &app,
           RefData      data,
           SlotActivate slot_activate) :
  gpyp::MenuShell(app, data, slot_activate)
{
  for(ConfMenu::Menu::iterator iter = data->begin();
      iter != data->end(); ++iter) {
    on_item_added(*iter);
  }
}

//========================
// YPMenu::MenuItem
void YPMenu::MenuItem::yp_enable_on_changed() {
  property_visible() = *yp_->enable();
}

YPMenu::MenuItem::MenuItem(RefYellowPage yp) :
  SimpleMenuItem(yp->name(), yp->icon()),
  yp_           (yp)
{
  set_no_show_all(true);
  yp_enable_on_changed();
  yp_->enable()->signal_changed()
    .connect(sigc::mem_fun(*this, &MenuItem::yp_enable_on_changed));
}

//========================
// YPMenu
Gtk::MenuItem *YPMenu::create_item(RefYellowPage yp) {
  return Gtk::manage(new MenuItem(yp));
}

void YPMenu::on_activate(RefYellowPage yp, CommandOpts &opts) {
  opts.yp = yp;
}

YPMenu::YPMenu(Application &app) :
  CollectionMenu<YPCollection>(app,
                               app.ypcollection(),
                               app.conf().menu()->yp()),
  reload_all_(_("_Reload all"),
              sigc::mem_fun(*app.ypcollection(), &YPCollection::reload_all),
              Gtk::Stock::REFRESH)
{
  prepend(reload_all_);
}

//========================
// ChannelMenu
void ChannelMenu::on_activate(CommandOpts &opts) {
  opts.ch = app_.get_selected_channel();
}

ChannelMenu::ChannelMenu(Application &app) :
  Menu(app, app.conf().menu()->ch()),
  app_(app)
{
}

//========================
// ToolMenu
void ToolMenu::on_activate(CommandOpts &opts) {
  opts.ch = app_.get_selected_channel();
}

ToolMenu::ToolMenu(Application &app) :
  Menu(app, app.conf().menu()->tool()),
  app_(app)
{
}

//========================
// StatusIconMenu
StatusIconMenu::StatusIconMenu(Application &app) :
  ToolMenu(app),
  quit_   (_("_Quit"), sigc::ptr_fun(Gtk::Main::quit), Gtk::Stock::QUIT)
{
  append(quit_);
}

//========================
// ViewMenu
ViewMenu::ViewMenu(MainWindow &mainwindow, ChannelList &channellist) :
  mainwindow_   (mainwindow),
  model_default_(_("_Default"),        channellist.model(), CLMODEL_DEFAULT),
  model_played_ (_("_Played history"), channellist.model(), CLMODEL_PLAYED),
  model_ended_  (_("_Ended channels"), channellist.model(), CLMODEL_ENDED),

  panel_left_   (_("_Left"),           mainwindow.panel_position(), PANEL_POSITION_LEFT),
  panel_right_  (_("_Right"),          mainwindow.panel_position(), PANEL_POSITION_RIGHT),
  panel_top_    (_("_Top"),            mainwindow.panel_position(), PANEL_POSITION_TOP),
  panel_bottom_ (_("_Bottom"),         mainwindow.panel_position(), PANEL_POSITION_BOTTOM),
  panel_none_   (_("_Disable"),        mainwindow.panel_position(), PANEL_POSITION_NONE),

  menubar_      (_("_Menu bar"),       mainwindow_.show_menubar()),
  filterbar_    (_("_Filter bar"),     mainwindow_.show_filterbar()),
  statusbar_    (_("_Status bar"),     mainwindow_.show_statusbar())
{
  append(model_default_);
  append(model_played_);
  append(model_ended_);

  append(*Gtk::manage(new Gtk::SeparatorMenuItem()));

  append(menubar_);
  append(filterbar_);
  append(statusbar_);

  append(*Gtk::manage(new Gtk::SeparatorMenuItem()));

  Gtk::Menu *menu_panel = Gtk::manage(new Gtk::Menu());
  menu_panel->append(panel_left_);
  menu_panel->append(panel_right_);
  menu_panel->append(panel_top_);
  menu_panel->append(panel_bottom_);
  menu_panel->append(panel_none_);
  Gtk::MenuItem *mi_panel = Gtk::manage(new Gtk::MenuItem(_("_Panel position"), true));
  mi_panel->set_submenu(*menu_panel);
  append(*mi_panel);

  show_all();
}

//========================
// SettingsMenu
SettingsMenu::SettingsMenu(Application &app, MainWindow &mainwindow) :
  auto_play_  (_("_Auto play"), app.auto_play()),
  mute_       (_("_Mute"),      app.conf().sound()->mute()),
  resizable_  (_("Window _resizable"), mainwindow.resizable()),
  pref_       (_("_Preferences"),
               sigc::mem_fun(mainwindow, &MainWindow::show_pref),
               Gtk::Stock::PREFERENCES),
  config_view_(_("_Config viewer"),
               sigc::mem_fun(mainwindow, &MainWindow::show_config_view)),
  save_       (_("_Save"),
               sigc::mem_fun(app.conf(), &Conf::save),
               Gtk::Stock::SAVE)
{
  append(auto_play_);
  append(mute_);
  append(resizable_);
  append(pref_);
  append(config_view_);
  append(save_);
}

//========================
// PlayScriptDialog
#ifdef ENABLE_DBUS_SERVICE
class PlayScriptDialog : public Gtk::Dialog {
private:
  Gtk::TextView       view_;
  Gtk::ScrolledWindow sw_view_;

public:
  virtual void on_response(int response_id) {
    if (response_id != Gtk::RESPONSE_APPLY) return;
    Gtk::FileChooserDialog d(*this, "Save", Gtk::FILE_CHOOSER_ACTION_SAVE);
    d.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
    d.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
    d.set_filename("play.sh");
    if (d.run() == Gtk::RESPONSE_OK) {
      Glib::ustring filename = d.get_filename();
      Glib::ustring contents = view_.get_buffer()->get_text();
      GError *err = NULL;
      if (g_file_set_contents(filename.c_str(),
                              contents.c_str(),
                              -1,
                              &err)) {
        chmod(filename.c_str(), 0755);
      } else {
        log::err << "failed to write file - " << err->message << log::endl;
      }
      if (err) g_error_free(err);
    }
  }

public:
  PlayScriptDialog(Gtk::Window &parent) :
    Gtk::Dialog(_("play.sh"), parent)
  {
    view_.get_buffer()->set_text("#!/bin/sh\n"
                                 "dbus-send "
                                 "--type=method_call "
                                 "--dest="DBUS_INTERFACE" "
                                 DBUS_PATH" "
                                 DBUS_INTERFACE".Play "
                                 "string:\"$1\"");
    view_.set_wrap_mode(Gtk::WRAP_WORD);

    sw_view_.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
    sw_view_.add(view_);

    get_vbox()->pack_start(sw_view_, true, true);

    add_button(Gtk::Stock::SAVE,  Gtk::RESPONSE_APPLY);
    add_button(Gtk::Stock::CLOSE, Gtk::RESPONSE_CLOSE);
    set_default_response(Gtk::RESPONSE_CLOSE);
    resize(480, 320);
    show_all();
  }
};
#endif

//========================
// HelpMenu
#ifdef ENABLE_DBUS_SERVICE
void HelpMenu::play_script() {
  static DialogWrapper<PlayScriptDialog> dialog;
  if (!dialog) {
    dialog.reset(new PlayScriptDialog(mainwindow_));
  }
  dialog->show();
}
#endif

HelpMenu::HelpMenu(MainWindow &mainwindow) :
  mainwindow_    (mainwindow),
  mi_bc_         (_("_Builtin commands"),
                  sigc::mem_fun(mainwindow, &MainWindow::show_help_bc)),
  mi_opts_       (_("Commandline _options"),
                  sigc::mem_fun(mainwindow, &MainWindow::show_help_opts)),
  mi_env_info_   (_("_Environment information"),
                  sigc::mem_fun(mainwindow, &MainWindow::show_env_info)),
#ifdef ENABLE_DBUS_SERVICE
  mi_play_script_(_("play.sh"),
                  sigc::mem_fun(*this, &HelpMenu::play_script)),
#endif
  mi_about_      (_("_About"),
                  sigc::mem_fun(mainwindow, &MainWindow::show_about),
                  Gtk::Stock::ABOUT)
{
  append(mi_bc_);
  append(mi_opts_);
  append(mi_env_info_);
#ifdef ENABLE_DBUS_SERVICE
  append(mi_play_script_);
#endif
  append(mi_about_);
}

//========================
// ChannelListMenu
void ChannelListMenu::show_menubar_changed() {
  if (*mainwindow_.show_menubar()) {
    get_separator_back()->hide();
    mi_yp_      .hide();
    mi_tool_    .hide();
    mi_view_    .hide();
    mi_settings_.hide();
    mi_help_    .hide();
  } else {
    get_separator_back()->show();
    mi_yp_      .show();
    mi_tool_    .show();
    mi_view_    .show();
    mi_settings_.show();
    mi_help_    .show();
  }
}

ChannelListMenu::ChannelListMenu(Application &app,
                                 MainWindow  &mainwindow,
                                 ChannelList &channellist) :
  ChannelMenu   (app),
  mainwindow_   (mainwindow),
  mi_yp_        (_("_YellowPage"), true),
  menu_yp_      (app),
  mi_tool_      (_("_Tool"), true),
  menu_tool_    (app),
  mi_view_      (_("_View"), true),
  menu_view_    (mainwindow_, channellist),
  mi_settings_  (_("_Settings"), true),
  menu_settings_(app, mainwindow),
  mi_help_      (_("_Help"), true),
  menu_help_    (mainwindow_)
{
  mi_yp_      .set_submenu(menu_yp_);
  mi_tool_    .set_submenu(menu_tool_);
  mi_view_    .set_submenu(menu_view_);
  mi_settings_.set_submenu(menu_settings_);
  mi_help_    .set_submenu(menu_help_);

  append(mi_yp_);
  append(mi_tool_);
  append(mi_view_);
  append(mi_settings_);
  append(mi_help_);

  show_menubar_changed();
  conn_show_menubar_changed_ = mainwindow.show_menubar()->signal_changed()
    .connect(sigc::mem_fun(*this, &ChannelListMenu::show_menubar_changed));
}
ChannelListMenu::~ChannelListMenu() {
  conn_show_menubar_changed_.disconnect();
}

//========================
// ColorSelectMenu::Item
void ColorSelectMenu::Item::on_toggled() {
  Gtk::RadioMenuItem::on_toggled();
  if (get_active()) signal_selected_(color_);
}

ColorSelectMenu::Item::Item(Group &group, const Glib::ustring &label) :
  Gtk::RadioMenuItem(group),
  label_            (label, Gtk::ALIGN_LEFT)
{
  add(label_);
}
ColorSelectMenu::Item::Item(Group               &group,
                            const Color         &color,
                            const Glib::ustring &label) :
  Gtk::RadioMenuItem(group),
  label_            (label, Gtk::ALIGN_LEFT),
  color_            (color)
{
  box_.modify_bg(Gtk::STATE_NORMAL,   color);
  box_.modify_bg(Gtk::STATE_PRELIGHT, color);
  if (!label.empty()) {
    box_.add(label_);
    if (color.get_brightness() < 0x3000) {
      Gdk::Color white("white");
      label_.modify_fg(Gtk::STATE_NORMAL,   white);
      label_.modify_fg(Gtk::STATE_PRELIGHT, white);
    }
  }
  add(box_);
}

//========================
// ColorSelectMenu
void ColorSelectMenu::on_selected(const Color &color) {
  signal_selected_(color);
}
void ColorSelectMenu::custom_on_toggled() {
  if (custom_.get_active()) {
    Gtk::ColorSelectionDialog d;
    d.get_colorsel()->set_current_color(color_);
    if (d.run() == Gtk::RESPONSE_OK) {
      signal_selected_(d.get_colorsel()->get_current_color());
    }
  }
}

ColorSelectMenu::ColorSelectMenu(Application &app, const Color &color) :
  color_ (color),
  none_  (group_, _("None")),
  custom_(group_, color, _("Custom"))
{
  bool selected = false;
  append(none_);
  if (!color) {
    none_.set_active(true);
    selected = true;
  }
  none_.signal_selected()
    .connect(sigc::mem_fun(*this, &ColorSelectMenu::on_selected));

  ConfMain::RefPresetColors colors = app.conf().preset_colors();
  for(ConfMain::PresetColors::iterator iter = colors->begin();
      iter != colors->end(); ++iter) {
    const Color &c = *(*iter)->color();
    Item *item = Gtk::manage(new Item(group_, c));
    append(*item);
    if (!selected && c == color) {
      item->set_active(true);
      selected = true;
    }
    item->signal_selected()
      .connect(sigc::mem_fun(*this, &ColorSelectMenu::on_selected));
  }

  append(custom_);
  if (!selected) custom_.set_active(true);
  custom_.signal_toggled()
    .connect(sigc::mem_fun(*this, &ColorSelectMenu::custom_on_toggled));
  show_all();
}

//========================
// ChSortMenu
void ChSortMenu::mi_on_toggled(GtkCheckMenuItem *mi, gpointer data) {
  if (gtk_check_menu_item_get_active(mi)) {
    ((ChSortMenu *)data)->on_changed();
  }
}

void ChSortMenu::on_changed() {
  signal_changed_(get());
}

ChSort ChSortMenu::get() {
#define GET_ACTIVE(I) gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(I))
  if (GET_ACTIVE(mi_top_)) {
    return CHSORT_TOP;
  } else if (GET_ACTIVE(mi_middle_)) {
    return CHSORT_MIDDLE;
  } else if (GET_ACTIVE(mi_bottom_)) {
    return CHSORT_BOTTOM;
  } else {
    return CHSORT_NONE;
  }
#undef GET_ACTIVE
}
void ChSortMenu::set(ChSort sort) {
#define ACTIVATE(I) gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(I), TRUE)
  switch(sort) {
  case CHSORT_TOP:    ACTIVATE(mi_top_);    break;
  case CHSORT_MIDDLE: ACTIVATE(mi_middle_); break;
  case CHSORT_BOTTOM: ACTIVATE(mi_bottom_); break;
  default:            ACTIVATE(mi_none_);   break;
  }
#undef ACTIVATE
}

#define RMI gtk_radio_menu_item_new_with_mnemonic
#define RG gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(mi_none_))
ChSortMenu::ChSortMenu() :
  mi_none_  (RMI(NULL, _("_None"))),
  mi_top_   (RMI(RG,   _("_Top"))),
  mi_middle_(RMI(RG,   _("_Middle"))),
  mi_bottom_(RMI(RG,   _("_Bottom")))
{
#define CONN_ACTIVATE(I) g_signal_connect(I, "toggled", G_CALLBACK(&ChSortMenu::mi_on_toggled), (gpointer)this)
  CONN_ACTIVATE(mi_none_);
  CONN_ACTIVATE(mi_top_);
  CONN_ACTIVATE(mi_middle_);
  CONN_ACTIVATE(mi_bottom_);
#undef CONN_ACTIVATE

  gtk_menu_append(gobj(), mi_none_);
  gtk_menu_append(gobj(), mi_top_);
  gtk_menu_append(gobj(), mi_middle_);
  gtk_menu_append(gobj(), mi_bottom_);
}
#undef RG
#undef RMI

//========================
// PlayerMenuItem
void PlayerMenuItem::icon_on_changed() {
  const Glib::ustring &icon_name = *player_->icon();
  if (icon_name.empty()) {
    image_.clear();
  } else {
    image_.set(IconManager::load_icon(icon_name, GTK_ICON_SIZE_MENU));
  }
}
void PlayerMenuItem::auto_play_on_changed() {
}

PlayerMenuItem::PlayerMenuItem(RefPlayer player) :
  player_(player)
{
#ifdef GTK_MENU_ITEM_HAVE_LABEL_PROPERTY
  player->name()->attach(property_label());
#else
  /* from gtkaction.c */
  Gtk::Widget *child = get_child();
  Gtk::Label  *label = dynamic_cast<Gtk::Label *>(child);
  if (!label) {
    if (child) remove();
    label = Gtk::manage(new Gtk::Label());
    add(*label);
  }
  player->name()->attach(label->property_label());
  label->property_xalign() = 0;
  label->show();
#endif

  icon_on_changed();
  conn_icon_changed_ = player_->icon()->signal_changed()
    .connect(sigc::mem_fun(*this, &PlayerMenuItem::icon_on_changed));
  conn_auto_play_changed_ = player_->auto_play()->signal_changed()
    .connect(sigc::mem_fun(*this, &PlayerMenuItem::auto_play_on_changed));

  set_image(image_);
}
PlayerMenuItem::~PlayerMenuItem() {
  conn_icon_changed_.disconnect();
  conn_auto_play_changed_.disconnect();
}

//========================
// PlayWithMenu::Item
void PlayWithMenu::Item::on_activate() {
  CommandOpts opts;
  if (!slot_activate_.empty()) slot_activate_(opts);
  app_.commandrunner().run(*get_player()->command(), &opts);
}

void PlayWithMenu::Item::auto_play_on_changed() {
  PlayerMenuItem::auto_play_on_changed();
  property_visible() = !*get_player()->auto_play();
}

PlayWithMenu::Item::Item(Application &app,
                         RefPlayer    player,
                         SlotActivate slot_activate) :
  PlayerMenuItem(player),
  app_          (app),
  slot_activate_(slot_activate)
{
  auto_play_on_changed();
}

//========================
// PlayWithMenu
void PlayWithMenu::on_item_added(RefPlayer player) {
  add(*Gtk::manage(new Item(app_, player, slot_activate_)));
}
void PlayWithMenu::on_item_removed(RefPlayer player) {
  remove(player, PlayerMatch());
}
void PlayWithMenu::on_item_reordered(RefPlayer player,
                                     RefPlayer prev,
                                     RefPlayer next) {
  reorder(player, prev, next, PlayerMatch());
}
void PlayWithMenu::on_cleared() {
  clear();
}

void PlayWithMenu::setting_on_clicked() {
  if (!setting_dialog_) {
    setting_dialog_.reset(new pref::PlayerDialog(app_, *app_.mainwindow()));
  }
  setting_dialog_->show();
}

PlayWithMenu::PlayWithMenu(Application &app,
                           SlotActivate slot_activate) :
  app_          (app),
  slot_activate_(slot_activate),
  mi_setting_   (_("Setting"),
                 sigc::mem_fun(*this, &PlayWithMenu::setting_on_clicked),
                 Gtk::Stock::PREFERENCES)
{
  ConfMain::RefPlayerCollection pc = app_.conf().command()->players();
  for(ConfMain::PlayerCollection::iterator iter = pc->begin();
      iter != pc->end(); ++iter) {
    on_item_added(*iter);
  }

#define PC_CONN(N) conn_##N##_ = pc->signal_##N().connect(sigc::mem_fun(*this, &PlayWithMenu::on_##N))
  PC_CONN(item_added);
  PC_CONN(item_removed);
  PC_CONN(item_reordered);
  PC_CONN(cleared);
#undef PC_CONN

  append(mi_setting_);
}
PlayWithMenu::~PlayWithMenu() {
  conn_item_added_    .disconnect();
  conn_item_removed_  .disconnect();
  conn_item_reordered_.disconnect();
  conn_cleared_       .disconnect();
}

}/*gpyp*/
